// Source File Name:   TSImpl.java

package com.sunwayhorizo.health.hl7.datatypes.common;

import com.sunwayhorizo.health.hl7.common.Hl7Exception;
import com.sunwayhorizo.health.hl7.common.VersionInfo;
import com.sunwayhorizo.health.hl7.datatypes.ANY;
import com.sunwayhorizo.health.hl7.datatypes.BL;
import com.sunwayhorizo.health.hl7.datatypes.CS;
import com.sunwayhorizo.health.hl7.datatypes.INT;
import com.sunwayhorizo.health.hl7.datatypes.PQ;
import com.sunwayhorizo.health.hl7.datatypes.QTY;
import com.sunwayhorizo.health.hl7.datatypes.REAL;
import com.sunwayhorizo.health.hl7.datatypes.TS;
import com.sunwayhorizo.health.hl7.datatypes.TYPE;
import com.sunwayhorizo.health.hl7.datatypes.common.gtsparser.TimeParser;

import java.io.StringReader;

import java.math.BigDecimal;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

//            GTSBase, ANYImpl, BLImpl, CSImpl,
//            INTImpl, NullFlavorImpl, PQImpl, REALBigDecimalImpl, 
//            REALImpl, STImpl, TYPEImpl

public class TSImpl extends GTSBase implements TS {

	private static final long serialVersionUID = -7356363282539712673L;
	public static final String RCS_ID = "$Header: TSImpl.java 1.0 2006/09/16 09:23:26 amchanda noship $";
	public static final boolean RCS_ID_RECORDED = VersionInfo.recordClassVersion(
					"$Header: TSImpl.java 1.0 2006/09/16 09:23:26 amchanda noship $",
					"com.sunwayhorizo.health.hl7.datatypes.common");
	public static final INT YEAR_PRECISION = INTImpl.valueOf(4L);
	public static final INT MONTH_PRECISION = INTImpl.valueOf(6L);
	public static final INT DAY_PRECISION = INTImpl.valueOf(8L);
	public static final INT HR_PRECISION = INTImpl.valueOf(10L);
	public static final INT MIN_PRECISION = INTImpl.valueOf(12L);
	public static final INT SEC_PRECISION = INTImpl.valueOf(14L);
	public static final CS YEAR = CSImpl.valueOf("Y");
	public static final CS MONTH = CSImpl.valueOf("M");
	public static final CS DAY = CSImpl.valueOf("D");
	public static final CS HOUR = CSImpl.valueOf("H");
	public static final CS MINUTE = CSImpl.valueOf("N");
	public static final CS SECOND = CSImpl.valueOf("S");
	public static final CS WEEK = CSImpl.valueOf("W");
	private static final String YEAR_FORMAT = "yyyy";
	private static final String MONTH_FORMAT = "yyyyMM";
	private static final String DAY_FORMAT = "yyyyMMdd";
	private static final String HR_FORMAT = "yyyyMMddHH";
	private static final String MIN_FORMAT = "yyyyMMddHHmm";
	private static final String SEC_FORMAT = "yyyyMMddHHmmss";
	private static final String FRAC_FORMAT = "yyyyMMddHHmmss.SSS";
	private static final BigDecimal hours = new BigDecimal(3600D);
	private static final BigDecimal mins = new BigDecimal(60D);
	private Date mOffset;
	private INT mPrecision;
	private PQ mTimezone;
	private static final TS nullNI = new TSImpl(CSImpl.valueOf("NI"));
	private static final TS nullOTH = new TSImpl(CSImpl.valueOf("OTH"));
	private static final TS nullNINF = new TSImpl(CSImpl.valueOf("NINF"));
	private static final TS nullPINF = new TSImpl(CSImpl.valueOf("PINF"));
	private static final TS nullUNK = new TSImpl(CSImpl.valueOf("UNK"));
	private static final TS nullASKU = new TSImpl(CSImpl.valueOf("ASKU"));
	private static final TS nullNAV = new TSImpl(CSImpl.valueOf("NAV"));
	private static final TS nullMSK = new TSImpl(CSImpl.valueOf("MSK"));
	private static final TS nullNA = new TSImpl(CSImpl.valueOf("NA"));
	private static final TS nullNASK = new TSImpl(CSImpl.valueOf("NASK"));
	private static final DateFormat df = new SimpleDateFormat(
			"yyyyMMddHHmmss.SSS");

	public static CS nextLower(CS cs) {
		if (cs.equal(YEAR).isTrue())
			return MONTH;
		if (cs.equal(MONTH).isTrue())
			return DAY;
		if (cs.equal(DAY).isTrue())
			return HOUR;
		if (cs.equal(HOUR).isTrue())
			return MINUTE;
		if (cs.equal(MINUTE).isTrue())
			return SECOND;
		else
			return null;
	}

	public static String nextHigher(CS cs) {
		if (cs.equal(SECOND).isTrue())
			return "min";
		if (cs.equal(MINUTE).isTrue())
			return "h";
		if (cs.equal(HOUR).isTrue())
			return "d";
		if (cs.equal(DAY).isTrue())
			return "mo";
		if (cs.equal(MONTH).isTrue())
			return "ann";
		else
			return null;
	}

	public static CS twoLetter(CS cs) {
		if (cs.equal(SECOND).isTrue())
			return CSImpl.valueOf("SN");
		if (cs.equal(MINUTE).isTrue())
			return CSImpl.valueOf("NH");
		if (cs.equal(HOUR).isTrue())
			return CSImpl.valueOf("HD");
		if (cs.equal(DAY).isTrue())
			return CSImpl.valueOf("DM");
		if (cs.equal(MONTH).isTrue())
			return CSImpl.valueOf("MY");
		if (cs.equal(YEAR).isTrue())
			return CSImpl.valueOf("CY");
		else
			return CSImpl.nullCS(NullFlavorImpl.OTH);
	}

	public static String calendarCycleUOM(CS cs) {
		if (cs.equal(SECOND).isTrue())
			return "s";
		if (cs.equal(MINUTE).isTrue())
			return "min";
		if (cs.equal(HOUR).isTrue())
			return "h";
		if (cs.equal(DAY).isTrue())
			return "d";
		if (cs.equal(MONTH).isTrue())
			return "mo";
		if (cs.equal(YEAR).isTrue())
			return "ann";
		else
			return null;
	}

	protected TSImpl(Date date, INT int1, PQ pq) {
		if (date == null)
			throw ANYImpl.newNullException("TS", "constructor", "offset");
		if (int1 == null)
			throw ANYImpl.newNullException("TS", "constructor", "precision");
		mOffset = date;
		mPrecision = int1;
		mTimezone = pq;
		if (int1.equal(YEAR_PRECISION).or(int1.equal(MONTH_PRECISION))
				.or(int1.equal(DAY_PRECISION)).or(int1.equal(HR_PRECISION))
				.or(int1.equal(MIN_PRECISION)).or(int1.equal(SEC_PRECISION))
				.isFalse()) {
			long l = SEC_PRECISION.longValue();
			if (int1.longValue() < l
					|| literal().stringValue().charAt((int) l) != '.')
				throw ANYImpl.newTSPrecisionException("TS", int1.literal()
						.stringValue());
		}
	}

	protected TSImpl(CS cs) {
		super(cs);
		mOffset = null;
		mPrecision = INTImpl.nullINT(NullFlavorImpl.NI);
		mTimezone = PQImpl.nullPQ(NullFlavorImpl.NI);
	}

	public TSImpl(Calendar calendar) {
		mOffset = calendar.getTime();
		mPrecision = INTImpl.valueOf(18l);
		mTimezone = null;
	}

	protected String generateLiteral() {
		SimpleDateFormat simpledateformat;
		if (mPrecision.longValue() > SEC_PRECISION.longValue())
			simpledateformat = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
		else
			simpledateformat = new SimpleDateFormat(
					"yyyyMMddHHmmss.SSS".substring(0, mPrecision.intValue()));
		StringBuffer stringbuffer = new StringBuffer(
				simpledateformat.format(mOffset));
		if (mTimezone.isNull().isFalse()) {
			long l = mTimezone.value().bigDecimalValue().longValue();
			if (l == 0L) {
				stringbuffer.append("+00");
			} else {
				if (l > 0L)
					stringbuffer.append("+");
				else
					stringbuffer.append("-");
				long l1 = Math.abs(l / 3600L);
				long l2 = Math.abs(l % 3600L) / 60L;
				if (l1 < 10L)
					stringbuffer.append("0");
				stringbuffer.append(Long.toString(l1));
				if (l2 > 0L) {
					if (l2 < 10L)
						stringbuffer.append("0");
					stringbuffer.append(Long.toString(l2));
				} else {
					stringbuffer.append("00");
				}
			}
		}
		return stringbuffer.toString();
	}

	public static TS valueOf(Date date, INT int1, PQ pq) {
		return new TSImpl(date, int1, pq);
	}

	public static TS nullTS() {
		return new TSImpl(NullFlavorImpl.NI);
	}

	public static TS valueOf(Date date) {
		return valueOf(date, SEC_PRECISION);
	}

	public static TS valueOf(Date date, INT int1) {
		return new TSImpl(date, int1, PQImpl.nullPQ(NullFlavorImpl.NI));
	}

	public static TS valueOf(String s) throws Hl7Exception {
		StringReader stringreader = new StringReader(s);
		TimeParser timeparser = new TimeParser(stringreader);
		try {
			TS ts = timeparser.ts();
			GTSBase.checkParserState(timeparser);
			((GTSBase) ts)._literal = STImpl.valueOf(s);
			((GTSBase) ts)._dataType = com.sunwayhorizo.health.hl7.datatypes.common.TYPEImpl.TS;
			return ts;
		} catch (Throwable throwable) {
			throw ANYImpl.newParseException("TS", s, throwable);
		}
	}

	public static TS getTSByString(String s) throws Hl7Exception {
		Date date = null;
		try {
			date = new SimpleDateFormat("yyyyMMddHHmmss").parse(s);
		} catch (ParseException e) {

			e.printStackTrace();
			return nullTS(null);
		}
		return valueOf(date);
	}

	public static TS valueOf(String s, String s1) {
		int i = s.length();
		INT int1 = null;
		String s2 = "yyyyMMddHHmmss.SSS";
		switch (i) {
		case 4: // '\004'
			int1 = YEAR_PRECISION;
			s2 = "yyyy";
			break;

		case 6: // '\006'
			int1 = MONTH_PRECISION;
			s2 = "yyyyMM";
			break;

		case 8: // '\b'
			int1 = DAY_PRECISION;
			s2 = "yyyyMMdd";
			break;

		case 10: // '\n'
			int1 = HR_PRECISION;
			s2 = "yyyyMMddHH";
			break;

		case 12: // '\f'
			int1 = MIN_PRECISION;
			s2 = "yyyyMMddHHmm";
			break;

		case 14: // '\016'
			int1 = SEC_PRECISION;
			s2 = "yyyyMMddHHmmss";
			break;

		default:
			int1 = INTImpl.valueOf(i);
			break;
		}
		String s3 = s;
		if (i > 14) {
			int1 = INTImpl.valueOf(i - 1);
			if (i == 16)
				s3 = s + "00";
			else if (i == 17)
				s3 = s + "0";
			if (i > 18)
				s3 = s.substring(0, 18);
		}
		SimpleDateFormat simpledateformat = new SimpleDateFormat(s2);
		simpledateformat.setLenient(false);
		ParsePosition parseposition = new ParsePosition(0);
		Date date = simpledateformat.parse(s3, parseposition);
		if (date == null)
			throw ANYImpl.newDateException(s3);
		PQ pq = parseTimezoneOffset(s1);
		if (pq == null)
			throw ANYImpl.newDateException(s3);
		TS ts = valueOf(date, int1, pq);
		if (s1 != null)
			((GTSBase) ts)._literal = STImpl.valueOf(s + s1);
		else
			((GTSBase) ts)._literal = STImpl.valueOf(s);
		return ts;
	}

	public static TS current() {
		return valueOf(new Date());
	}

	public TYPE dataType() {
		return com.sunwayhorizo.health.hl7.datatypes.common.TYPEImpl.TS;
	}

	public BL equal(ANY any) {
		if (!(any instanceof TS))
			return BLImpl.FALSE;
		if (isNull().or(any.isNull()).isTrue()) {
			return BLImpl.FALSE;
		} else {
			TS ts = (TS) any;
			precision().equal(ts.precision()).isFalse();
			long l = compareWithTZ(ts);
			return BLImpl.valueOf(l == 0L);
		}
	}

	public BL compares(QTY qty) {
		return BLImpl.valueOf(qty instanceof TS);
	}

	public TYPE diffType() {
		return com.sunwayhorizo.health.hl7.datatypes.common.TYPEImpl.PQ;
	}

	public QTY minus(QTY qty) {
		if (!(qty instanceof TS) && !(qty instanceof PQ))
			throw ANYImpl.newUnsupportedException("TS", "minus");
		if (qty instanceof TS) {
			TS ts = (TS) qty;
			if (precision().equal(ts.precision()).isFalse()) {
				throw ANYImpl.newTSPrecisionException("TS", ts.precision()
						.literal().stringValue());
			} else {
				BigDecimal bigdecimal = new BigDecimal(compareWithTZ(ts));
				REAL real = REALBigDecimalImpl.valueOf(bigdecimal);
				CS cs = CSImpl.valueOf("ms");
				return PQImpl.valueOf(real, cs);
			}
		} else {
			return minusPlusPQHelper((PQ) qty, true);
		}
	}

	public QTY plus(QTY qty) {
		if (!(qty instanceof PQ))
			throw ANYImpl.newUnsupportedException("TS", "plus");
		else
			return minusPlusPQHelper((PQ) qty, false);
	}

	private TS minusPlusPQHelper(PQ pq, boolean flag) {
		BigDecimal bigdecimal = pq.value().bigDecimalValue();
		if (flag)
			bigdecimal = bigdecimal.negate();
		long l = 0L;
		CS cs = pq.unit();
		if (cs.equal(CSImpl.valueOf("ms")).isTrue()
				|| cs.equal(CSImpl.valueOf("MS")).isTrue())
			l = mOffset.getTime() + bigdecimal.longValue();
		else if (cs.equal(CSImpl.valueOf("s")).isTrue()
				|| cs.equal(CSImpl.valueOf("S")).isTrue())
			l = mOffset.getTime() + bigdecimal.longValue() * 1000L;
		else if (cs.equal(CSImpl.valueOf("min")).isTrue()
				|| cs.equal(CSImpl.valueOf("MIN")).isTrue())
			l = mOffset.getTime() + bigdecimal.longValue() * 60L * 1000L;
		else if (cs.equal(CSImpl.valueOf("h")).isTrue()
				|| cs.equal(CSImpl.valueOf("HR")).isTrue())
			l = mOffset.getTime() + bigdecimal.longValue() * 60L * 60L * 1000L;
		else if (cs.equal(CSImpl.valueOf("d")).isTrue()
				|| cs.equal(CSImpl.valueOf("D")).isTrue())
			l = mOffset.getTime() + bigdecimal.longValue() * 24L * 60L * 60L
					* 1000L;
		else if (cs.equal(CSImpl.valueOf("wk")).isTrue()
				|| cs.equal(CSImpl.valueOf("WK")).isTrue())
			l = mOffset.getTime() + bigdecimal.longValue() * 7L * 24L * 60L
					* 60L * 1000L;
		else if (cs.equal(CSImpl.valueOf("mo")).isTrue()
				|| cs.equal(CSImpl.valueOf("MO")).isTrue()) {
			GregorianCalendar gregoriancalendar = (GregorianCalendar) Calendar
					.getInstance();
			gregoriancalendar.setTime(mOffset);
			gregoriancalendar.add(2, bigdecimal.intValue());
			l = gregoriancalendar.getTime().getTime();
		} else if (cs.equal(CSImpl.valueOf("a")).isTrue()
				|| cs.equal(CSImpl.valueOf("ANN")).isTrue()) {
			GregorianCalendar gregoriancalendar1 = (GregorianCalendar) Calendar
					.getInstance();
			gregoriancalendar1.setTime(mOffset);
			gregoriancalendar1.add(1, bigdecimal.intValue());
			l = gregoriancalendar1.getTime().getTime();
		} else {
			throw ANYImpl.newTSPrecisionException("TS", cs.literal()
					.stringValue());
		}
		return valueOf(new Date(l), precision(), timezone());
	}

	public BL isZero() {
		throw ANYImpl.newUnsupportedException("TS", "isZero");
	}

	public BL lessThan(QTY qty) {
		if (!(qty instanceof TS))
			return BLImpl.FALSE;
		if (isNull().or(qty.isNull()).isTrue()) {
			return BLImpl.FALSE;
		} else {
			TS ts = (TS) qty;
			precision().equal(ts.precision()).isFalse();
			long l = compareWithTZ(ts);
			return BLImpl.valueOf(l < 0L);
		}
	}

	public BL greaterOrEqual(QTY qty) {
		if (!(qty instanceof TS))
			return BLImpl.FALSE;
		if (isNull().or(qty.isNull()).isTrue()) {
			return BLImpl.FALSE;
		} else {
			TS ts = (TS) qty;
			precision().equal(ts.precision()).isFalse();
			long l = compareWithTZ(ts);
			return BLImpl.valueOf(l >= 0L);
		}
	}

	public BL greaterThan(QTY qty) {
		if (!(qty instanceof TS))
			return BLImpl.FALSE;
		if (isNull().or(qty.isNull()).isTrue()) {
			return BLImpl.FALSE;
		} else {
			TS ts = (TS) qty;
			precision().equal(ts.precision()).isFalse();
			long l = compareWithTZ(ts);
			return BLImpl.valueOf(l > 0L);
		}
	}

	public BL lessOrEqual(QTY qty) {
		if (!(qty instanceof TS))
			return BLImpl.FALSE;
		if (isNull().or(qty.isNull()).isTrue()) {
			return BLImpl.FALSE;
		} else {
			TS ts = (TS) qty;
			precision().equal(ts.precision()).isFalse();
			long l = compareWithTZ(ts);
			return BLImpl.valueOf(l <= 0L);
		}
	}

	public PQ offset() {
		return PQImpl.valueOf(
				REALImpl.valueOf(String.valueOf(mOffset.getTime())),
				CSImpl.valueOf("MS"));
	}

	public CS calendar() {
		return CSImpl.valueOf("GREG");
	}

	public INT precision() {
		return mPrecision;
	}

	public PQ timezone() {
		return mTimezone;
	}

	public Date toDate() {
		return mOffset;
	}

	public String toString() {
		if (isNull().isTrue())
			return "<Null:" + nullFlavor().toString() + ">";
		else
			return literal().stringValue();
	}

	public int hashCode() {
		int i = 17;
		if (isNull().isFalse()) {
			i = 37 * i + toDate().hashCode();
			i = 37 * i + precision().hashCode();
		}
		return i;
	}

	public static INT getPrecision(CS cs) {
		if (cs.equal(YEAR).isTrue())
			return YEAR_PRECISION;
		if (cs.equal(MONTH).isTrue())
			return MONTH_PRECISION;
		if (cs.equal(DAY).isTrue())
			return DAY_PRECISION;
		if (cs.equal(HOUR).isTrue())
			return HR_PRECISION;
		if (cs.equal(MINUTE).isTrue())
			return MIN_PRECISION;
		if (cs.equal(SECOND).isTrue())
			return SEC_PRECISION;
		else
			throw ANYImpl.newTSPrecisionException("TS", cs.literal()
					.stringValue());
	}

	protected static PQ parseTimezoneOffset(String s) {
		if (s == null || s.length() == 0)
			return PQImpl.nullPQ(NullFlavorImpl.NI);
		boolean flag = s.charAt(0) == '+';
		boolean flag1 = s.charAt(0) == '-';
		BigDecimal bigdecimal;
		switch (s.length()) {
		case 3: // '\003'
			if (!flag && !flag1)
				return null;
			// fall through

		case 2: // '\002'
			bigdecimal = (new BigDecimal(s)).multiply(hours);
			break;

		case 5: // '\005'
			if (!flag && !flag1)
				return null;
			BigDecimal bigdecimal1 = (new BigDecimal(s.substring(0, 3)))
					.multiply(hours);
			BigDecimal bigdecimal3 = (new BigDecimal(s.substring(3)))
					.multiply(mins);
			if (flag)
				bigdecimal = bigdecimal1.add(bigdecimal3);
			else
				bigdecimal = bigdecimal1.subtract(bigdecimal3);
			break;

		case 4: // '\004'
			BigDecimal bigdecimal2 = (new BigDecimal(s.substring(0, 2)))
					.multiply(hours);
			BigDecimal bigdecimal4 = (new BigDecimal(s.substring(2)))
					.multiply(mins);
			if (flag)
				bigdecimal = bigdecimal2.add(bigdecimal4);
			else
				bigdecimal = bigdecimal2.subtract(bigdecimal4);
			break;

		default:
			return null;
		}
		REAL real = REALBigDecimalImpl.valueOf(bigdecimal);
		return PQImpl.valueOf(real, CSImpl.valueOf("s"));
	}

	protected long compareWithTZ(TS ts) {
		long l = mOffset.getTime();
		long l1 = ts.toDate().getTime();
		if (timezone().isNull().xor(ts.timezone().isNull()).isTrue())
			throw ANYImpl.newTimezoneException(literal().stringValue(), ts
					.literal().stringValue());
		if (timezone().nonNull().and(ts.timezone().nonNull()).isTrue()) {
			l = minusPlusPQHelper(timezone(), true).toDate().getTime();
			l1 = ((TS) ts.minus(ts.timezone())).toDate().getTime();
		}
		return l - l1;
	}

	// public XMLNode[] writeXML(XMLDocument xmldocument, String s)
	// throws Hl7Exception
	// {
	// try
	// {
	// XMLElement xmlelement = (XMLElement)xmldocument.createElement(s);
	// xmlelement.setAttribute("value", toString());
	// return (new XMLNode[] {
	// xmlelement
	// });
	// }
	// catch(Exception exception)
	// {
	// throw new Hl7Exception(exception);
	// }
	// }

	public static TS nullTS(CS cs) {
		String s = null;
		if (cs == null || cs.literal().stringValue() == null
				|| cs.isNull().isTrue())
			s = "NI";
		// s = null;
		else
			s = cs.literal().stringValue();
		if (s.equals("NI"))
			return null;
		if (s.equals("OTH"))
			return nullOTH;
		if (s.equals("NINF"))
			return nullNINF;
		if (s.equals("PINF"))
			return nullPINF;
		if (s.equals("UNK"))
			return nullUNK;
		if (s.equals("ASKU"))
			return nullASKU;
		if (s.equals("NAV"))
			return nullNAV;
		if (s.equals("MSK"))
			return nullMSK;
		if (s.equals("NA"))
			return nullNA;
		if (s.equals("NASK"))
			return nullNASK;
		else
			return nullNI;
	}

	@Override
	// add by Obama
	// 当前TS对象根据时间格式转化成String
	public String toDateString(DateFormat df) {
		return df.format(toDate());
	}

	@Override
	// 同上，只不过格式为String形式
	public String toDateString(String format) {
		return new SimpleDateFormat(format).format(toDate());
	}

	// 同上，默认时间格式为yyyyMMddHHmmss.SSS
	@Override
	public String toDateString() {

		return df.format(toDate());
	}

	// 字符串转成TS,字符串格式必须为yyyyMMddHHmmss.SSS
	@Override
	public TS stringToTs(String date) {
		Date target = null;
		try {
			target = df.parse(date);
		} catch (ParseException e) {
			e.printStackTrace();
			return null;
		}
		return dateToTs(target);

	}

	// date直接转成TS
	@Override
	public TS dateToTs(Date date) {
		return valueOf(date);
	}

	// 以date符合fotmat的格式，转成TS
	@Override
	public TS toTs(String date, String format) {
		DateFormat df = new SimpleDateFormat(format);
		Date target = null;
		try {
			target = df.parse(date);
		} catch (ParseException e) {
			e.printStackTrace();
			return null;
		}
		return dateToTs(target);
	}
}
